Tests for various python features.     vim: set ft=vim :

STARTTEST
:so small.vim
:set noswapfile
:if !has('python3') || !has('quickfix') | e! test.ok | wq! test.out | endif
:lang C
:fun Test()
:py3 import vim
:py3 cb = vim.current.buffer
:let l = []
:py3 l=vim.bindeval('l')
:py3 f=vim.bindeval('function("strlen")')
:" Extending List directly with different types
:py3 l+=[1, "as'd", [1, 2, f, {'a': 1}]]
:$put =string(l)
:$put =string(l[-1])
:try
:  $put =string(l[-4])
:catch
:  $put =v:exception[:13]
:endtry
:" List assignment
:py3 l[0]=0
:$put =string(l)
:py3 l[-2]=f
:$put =string(l)
:"
:" Extending Dictionary directly with different types
:let d = {}
:fun d.f()
:  return 1
:endfun
py3 << EOF
d=vim.bindeval('d')
d['1']='asd'
d.update()  # Must not do anything, including throwing errors
d.update(b=[1, 2, f])
d.update((('-1', {'a': 1}),))
d.update({'0': -1})
dk = d.keys()
dv = d.values()
di = d.items()
dk.sort(key=repr)
dv.sort(key=repr)
di.sort(key=repr)
EOF
:$put =py3eval('d[''f''](self={})')
:$put =py3eval('repr(dk)')
:$put =substitute(py3eval('repr(dv)'),'0x\x\+','','g')
:$put =substitute(py3eval('repr(di)'),'0x\x\+','','g')
:for [key, Val] in sort(items(d))
:  $put =string(key) . ' : ' . string(Val)
:  unlet key Val
:endfor
:py3 del dk
:py3 del di
:py3 del dv
:"
:" removing items with del
:py3 del l[2]
:$put =string(l)
:let l = range(8)
:py3 l=vim.bindeval('l')
:try
:   py3 del l[:3]
:   py3 del l[1:]
:catch
:   $put =v:exception
:endtry
:$put =string(l)
:"
:py3 del d['-1']
:py3 del d['f']
:$put =string(py3eval('d.get(''b'', 1)'))
:$put =string(py3eval('d.pop(''b'')'))
:$put =string(py3eval('d.get(''b'', 1)'))
:$put =string(py3eval('d.pop(''1'', 2)'))
:$put =string(py3eval('d.pop(''1'', 2)'))
:$put =py3eval('repr(d.has_key(''0''))')
:$put =py3eval('repr(d.has_key(''1''))')
:$put =py3eval('repr(''0'' in d)')
:$put =py3eval('repr(''1'' in d)')
:$put =py3eval('repr(list(iter(d)))')
:$put =string(d)
:$put =py3eval('repr(d.popitem())')
:$put =py3eval('repr(d.get(''0''))')
:$put =py3eval('repr(list(iter(d)))')
:"
:" removing items out of range: silently skip items that don't exist
:let l = [0, 1, 2, 3]
:py3 l=vim.bindeval('l')
:" The following two ranges delete nothing as they match empty list:
:py3 del l[2:1]
:$put =string(l)
:py3 del l[2:2]
:$put =string(l)
:py3 del l[2:3]
:$put =string(l)
:let l = [0, 1, 2, 3]
:py3 l=vim.bindeval('l')
:py3 del l[2:4]
:$put =string(l)
:let l = [0, 1, 2, 3]
:py3 l=vim.bindeval('l')
:py3 del l[2:5]
:$put =string(l)
:let l = [0, 1, 2, 3]
:py3 l=vim.bindeval('l')
:py3 del l[2:6]
:$put =string(l)
:let l = [0, 1, 2, 3]
:py3 l=vim.bindeval('l')
:" The following two ranges delete nothing as they match empty list:
:py3 del l[-1:2]
:$put =string(l)
:py3 del l[-2:2]
:$put =string(l)
:py3 del l[-3:2]
:$put =string(l)
:let l = [0, 1, 2, 3]
:py3 l=vim.bindeval('l')
:py3 del l[-4:2]
:$put =string(l)
:let l = [0, 1, 2, 3]
:py3 l=vim.bindeval('l')
:py3 del l[-5:2]
:$put =string(l)
:let l = [0, 1, 2, 3]
:py3 l=vim.bindeval('l')
:py3 del l[-6:2]
:$put =string(l)
:let l = [0, 1, 2, 3]
:py3 l=vim.bindeval('l')
:py3 del l[::2]
:$put =string(l)
:let l = [0, 1, 2, 3]
:py3 l=vim.bindeval('l')
:py3 del l[3:0:-2]
:$put =string(l)
:let l = [0, 1, 2, 3]
:py3 l=vim.bindeval('l')
:py3 del l[2:4:-2]
:$put =string(l)
:"
:" Slice assignment to a list
:let l = [0, 1, 2, 3]
:py3 l=vim.bindeval('l')
:py3 l[0:0]=['a']
:$put =string(l)
:let l = [0, 1, 2, 3]
:py3 l=vim.bindeval('l')
:py3 l[1:2]=['b']
:$put =string(l)
:let l = [0, 1, 2, 3]
:py3 l=vim.bindeval('l')
:py3 l[2:4]=['c']
:$put =string(l)
:let l = [0, 1, 2, 3]
:py3 l=vim.bindeval('l')
:py3 l[4:4]=['d']
:$put =string(l)
:let l = [0, 1, 2, 3]
:py3 l=vim.bindeval('l')
:py3 l[-1:2]=['e']
:$put =string(l)
:let l = [0, 1, 2, 3]
:py3 l=vim.bindeval('l')
:py3 l[-10:2]=['f']
:$put =string(l)
:let l = [0, 1, 2, 3]
:py3 l=vim.bindeval('l')
:py3 l[2:-10]=['g']
:$put =string(l)
:let l = []
:py3 l=vim.bindeval('l')
:py3 l[0:0]=['h']
:$put =string(l)
:let l = range(8)
:py3 l=vim.bindeval('l')
:py3 l[2:6:2] = [10, 20]
:$put =string(l)
:let l = range(8)
:py3 l=vim.bindeval('l')
:py3 l[6:2:-2] = [10, 20]
:$put =string(l)
:let l = range(8)
:py3 l=vim.bindeval('l')
:py3 l[6:2] = ()
:$put =string(l)
:let l = range(8)
:py3 l=vim.bindeval('l')
:py3 l[6:2:1] = ()
:$put =string(l)
:let l = range(8)
:py3 l=vim.bindeval('l')
:py3 l[2:2:1] = ()
:$put =string(l)
:"
:" Locked variables
:let l = [0, 1, 2, 3]
:py3 l=vim.bindeval('l')
:lockvar! l
py3 << EOF
def emsg(ei):
    return ei[0].__name__ + ':' + repr(ei[1].args)

try:
    l[2]='i'
except vim.error:
    cb.append('l[2] threw vim.error: ' + emsg(sys.exc_info()))
EOF
:$put =string(l)
:unlockvar! l
:"
:" Function calls
py3 << EOF
import sys
import re

py33_type_error_pattern = re.compile('^__call__\(\) takes (\d+) positional argument but (\d+) were given$')
py37_exception_repr = re.compile(r'([^\(\),])(\)+)$')

def ee(expr, g=globals(), l=locals()):
    cb = vim.current.buffer
    try:
        try:
            exec(expr, g, l)
        except Exception as e:
            if sys.version_info >= (3, 3) and e.__class__ is AttributeError and str(e).find('has no attribute')>=0 and not str(e).startswith("'vim."):
                msg = repr((e.__class__, AttributeError(str(e)[str(e).rfind(" '") + 2:-1])))
            elif sys.version_info >= (3, 3) and e.__class__ is ImportError and str(e).find('No module named \'') >= 0:
                msg = repr((e.__class__, ImportError(str(e).replace("'", ''))))
            elif sys.version_info >= (3, 6) and e.__class__ is ModuleNotFoundError:
                # Python 3.6 gives ModuleNotFoundError, change it to an ImportError
                msg = repr((ImportError, ImportError(str(e).replace("'", ''))))
            elif sys.version_info >= (3, 3) and e.__class__ is TypeError:
                m = py33_type_error_pattern.search(str(e))
                if m:
                    msg = '__call__() takes exactly {0} positional argument ({1} given)'.format(m.group(1), m.group(2))
                    msg = repr((e.__class__, TypeError(msg)))
                else:
                    msg = repr((e.__class__, e))
                    # Messages changed with Python 3.6, change new to old.
                    newmsg1 = """'argument must be str, bytes or bytearray, not None'"""
                    oldmsg1 = '''"Can't convert 'NoneType' object to str implicitly"'''
                    if msg.find(newmsg1) > -1:
                        msg = msg.replace(newmsg1, oldmsg1)
                    newmsg2 = """'argument must be str, bytes or bytearray, not int'"""
                    oldmsg2 = '''"Can't convert 'int' object to str implicitly"'''
                    if msg.find(newmsg2) > -1:
                        msg = msg.replace(newmsg2, oldmsg2)
            elif sys.version_info >= (3, 5) and e.__class__ is ValueError and str(e) == 'embedded null byte':
                msg = repr((TypeError, TypeError('expected bytes with no null')))
            else:
                msg = repr((e.__class__, e))
                # Some Python versions say can't, others cannot.
                if msg.find('can\'t') > -1:
                    msg = msg.replace('can\'t', 'cannot')
                # Some Python versions use single quote, some double quote
                if msg.find('"cannot ') > -1:
                    msg = msg.replace('"cannot ', '\'cannot ')
                if msg.find(' attributes"') > -1:
                    msg = msg.replace(' attributes"', ' attributes\'')
            if sys.version_info >= (3, 7):
                msg = py37_exception_repr.sub(r'\1,\2', msg)
            cb.append(expr + ':' + msg)
        else:
            cb.append(expr + ':NOT FAILED')
    except Exception as e:
        msg = repr((e.__class__, e))
        if sys.version_info >= (3, 7):
            msg = py37_exception_repr.sub(r'\1,\2', msg)
        cb.append(expr + '::' + msg)
EOF
:fun New(...)
:   return ['NewStart']+a:000+['NewEnd']
:endfun
:fun DictNew(...) dict
:   return ['DictNewStart']+a:000+['DictNewEnd', self]
:endfun
:let l=[function('New'), function('DictNew')]
:py3 l=vim.bindeval('l')
:py3 l.extend(list(l[0](1, 2, 3)))
:$put =string(l)
:py3 l.extend(list(l[1](1, 2, 3, self={'a': 'b'})))
:$put =string(l)
:py3 l+=[l[0].name]
:$put =string(l)
:py3 ee('l[1](1, 2, 3)')
:py3 f=l[0]
:delfunction New
:py3 ee('f(1, 2, 3)')
:if has('float')
:   let l=[0.0]
:   py3 l=vim.bindeval('l')
:   py3 l.extend([0.0])
:   $put =string(l)
:else
:   $put ='[0.0, 0.0]'
:endif
:let messages=[]
:delfunction DictNew
py3 <<EOF
import sys
d=vim.bindeval('{}')
m=vim.bindeval('messages')
def em(expr, g=globals(), l=locals()):
    try:
        exec(expr, g, l)
    except Exception as e:
        if sys.version_info >= (3, 5) and e.__class__ is ValueError and str(e) == 'embedded null byte':
            m.extend([TypeError.__name__])
        else:
            m.extend([e.__class__.__name__])

em('d["abc1"]')
em('d["abc1"]="\\0"')
em('d["abc1"]=vim')
em('d[""]=1')
em('d["a\\0b"]=1')
em('d[b"a\\0b"]=1')

em('d.pop("abc1")')
em('d.popitem()')
del em
del m
EOF
:$put =messages
:unlet messages
:" locked and scope attributes
:let d={} | let dl={} | lockvar dl
:for s in split("d dl v: g:")
:    let name=tr(s, ':', 's')
:    execute 'py3 '.name.'=vim.bindeval("'.s.'")'
:    let toput=s.' : '.join(map(['locked', 'scope'], 'v:val.":".py3eval(name.".".v:val)'), ';')
:    $put =toput
:endfor
:silent! let d.abc2=1
:silent! let dl.abc3=1
:py3 d.locked=True
:py3 dl.locked=False
:silent! let d.def=1
:silent! let dl.def=1
:put ='d:'.string(d)
:put ='dl:'.string(dl)
:unlet d dl
:
:let l=[] | let ll=[] | lockvar ll
:for s in split("l ll")
:    let name=tr(s, ':', 's')
:    execute 'py3 '.name.'=vim.bindeval("'.s.'")'
:    let toput=s.' : locked:'.py3eval(name.'.locked')
:    $put =toput
:endfor
:silent! call extend(l, [0])
:silent! call extend(ll, [0])
:py3 l.locked=True
:py3 ll.locked=False
:silent! call extend(l, [1])
:silent! call extend(ll, [1])
:put ='l:'.string(l)
:put ='ll:'.string(ll)
:unlet l ll
:"
:" py3eval()
:let l=py3eval('[0, 1, 2]')
:$put =string(l)
:let d=py3eval('{"a": "b", "c": 1, "d": ["e"]}')
:$put =sort(items(d))
:let v:errmsg = ''
:$put ='py3eval(\"None\") = ' . py3eval('None') . v:errmsg
:if has('float')
:   let f=py3eval('0.0')
:   $put =string(f)
:else
:   $put ='0.0'
:endif
:" Invalid values:
:for e in ['"\0"', '{"\0": 1}', 'undefined_name', 'vim']
:   try
:      let v=py3eval(e)
:   catch
:      let toput=e.":\t".v:exception[:13]
:      $put =toput
:   endtry
:endfor
:"
:" threading
:let l = [0]
:py3 l=vim.bindeval('l')
py3 <<EOF
import threading
import time

class T(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.t = 0
        self.running = True

    def run(self):
        while self.running:
            self.t += 1
            time.sleep(0.1)

t = T()
del T
t.start()
EOF
:sleep 1
:py3 t.running = False
:py3 t.join()
:" Check if the background thread is working.  Count should be 10, but on a
:" busy system (AppVeyor) it can be much lower.
:py3 l[0] = t.t > 4
:py3 del time
:py3 del threading
:py3 del t
:$put =string(l)
:"
:" settrace
:let l = []
:py3 l=vim.bindeval('l')
py3 <<EOF
import sys

def traceit(frame, event, arg):
    global l
    if event == "line":
        l += [frame.f_lineno]
    return traceit

def trace_main():
    for i in range(5):
        pass
EOF
:py3 sys.settrace(traceit)
:py3 trace_main()
:py3 sys.settrace(None)
:py3 del traceit
:py3 del trace_main
:$put =string(l)
:"
:" Slice
:py3 ll = vim.bindeval('[0, 1, 2, 3, 4, 5]')
:py3 l = ll[:4]
:$put =string(py3eval('l'))
:py3 l = ll[2:]
:$put =string(py3eval('l'))
:py3 l = ll[:-4]
:$put =string(py3eval('l'))
:py3 l = ll[-2:]
:$put =string(py3eval('l'))
:py3 l = ll[2:4]
:$put =string(py3eval('l'))
:py3 l = ll[4:2]
:$put =string(py3eval('l'))
:py3 l = ll[-4:-2]
:$put =string(py3eval('l'))
:py3 l = ll[-2:-4]
:$put =string(py3eval('l'))
:py3 l = ll[:]
:$put =string(py3eval('l'))
:py3 l = ll[0:6]
:$put =string(py3eval('l'))
:py3 l = ll[-10:10]
:$put =string(py3eval('l'))
:py3 l = ll[4:2:-1]
:$put =string(py3eval('l'))
:py3 l = ll[::2]
:$put =string(py3eval('l'))
:py3 l = ll[4:2:1]
:$put =string(py3eval('l'))
:py3 del l
:"
:" Vars
:let g:foo = 'bac'
:let w:abc3 = 'def'
:let b:baz = 'bar'
:let t:bar = 'jkl'
:try
:  throw "Abc"
:catch
:  put =py3eval('vim.vvars[''exception'']')
:endtry
:put =py3eval('vim.vars[''foo'']')
:put =py3eval('vim.current.window.vars[''abc3'']')
:put =py3eval('vim.current.buffer.vars[''baz'']')
:put =py3eval('vim.current.tabpage.vars[''bar'']')
:"
:" Options
:" paste:          boolean, global
:" previewheight   number,  global
:" operatorfunc:   string,  global
:" number:         boolean, window-local
:" numberwidth:    number,  window-local
:" colorcolumn:    string,  window-local
:" statusline:     string,  window-local/global
:" autoindent:     boolean, buffer-local
:" shiftwidth:     number,  buffer-local
:" omnifunc:       string,  buffer-local
:" preserveindent: boolean, buffer-local/global
:" path:           string,  buffer-local/global
:let g:bufs=[bufnr('%')]
:new
:let g:bufs+=[bufnr('%')]
:vnew
:let g:bufs+=[bufnr('%')]
:wincmd j
:vnew
:let g:bufs+=[bufnr('%')]
:wincmd l
:fun RecVars(opt)
:  let gval =string(eval('&g:'.a:opt))
:  let wvals=join(map(range(1, 4),  'v:val.":".string(getwinvar(v:val, "&".a:opt))'))
:  let bvals=join(map(copy(g:bufs), 'v:val.":".string(getbufvar(v:val, "&".a:opt))'))
:  put ='  G: '.gval
:  put ='  W: '.wvals
:  put ='  B: '.wvals
:endfun
py3 << EOF
def e(s, g=globals(), l=locals()):
    try:
        exec(s, g, l)
    except Exception as e:
        vim.command('return ' + repr(e.__class__.__name__))

def ev(s, g=globals(), l=locals()):
    try:
        return eval(s, g, l)
    except Exception as e:
        vim.command('let exc=' + repr(e.__class__.__name__))
        return 0
EOF
:fun E(s)
:   python3 e(vim.eval('a:s'))
:endfun
:fun Ev(s)
:   let r=py3eval('ev(vim.eval("a:s"))')
:   if exists('exc')
:       throw exc
:   endif
:   return r
:endfun
:py3 gopts1=vim.options
:py3 wopts1=vim.windows[2].options
:py3 wopts2=vim.windows[0].options
:py3 wopts3=vim.windows[1].options
:py3 bopts1=vim.buffers[vim.bindeval("g:bufs")[2]].options
:py3 bopts2=vim.buffers[vim.bindeval("g:bufs")[1]].options
:py3 bopts3=vim.buffers[vim.bindeval("g:bufs")[0]].options
:$put ='wopts iters equal: '.py3eval('list(wopts1) == list(wopts2)')
:$put ='bopts iters equal: '.py3eval('list(bopts1) == list(bopts2)')
:py3 gset=set(iter(gopts1))
:py3 wset=set(iter(wopts1))
:py3 bset=set(iter(bopts1))
:set path=.,..,,
:let lst=[]
:let lst+=[['paste',          1,     0,     1,     2,      1,    1,      0    ]]
:let lst+=[['previewheight',  5,     1,     6,     'a',    0,    1,      0    ]]
:let lst+=[['operatorfunc',   'A',   'B',   'C',   2,      0,    1,      0    ]]
:let lst+=[['number',         0,     1,     1,     0,      1,    0,      1    ]]
:let lst+=[['numberwidth',    2,     3,     5,     -100,   0,    0,      1    ]]
:let lst+=[['colorcolumn',    '+1',  '+2',  '+3',  'abc4',  0,    0,      1    ]]
:let lst+=[['statusline',     '1',   '2',   '4',   0,      0,    1,      1    ]]
:let lst+=[['autoindent',     0,     1,     1,     2,      1,    0,      2    ]]
:let lst+=[['shiftwidth',     0,     2,     1,     3,      0,    0,      2    ]]
:let lst+=[['omnifunc',       'A',   'B',   'C',   1,      0,    0,      2    ]]
:let lst+=[['preserveindent', 0,     1,     1,     2,      1,    1,      2    ]]
:let lst+=[['path',           '.,,', ',,',  '.',   0,      0,    1,      2    ]]
:for       [oname,            oval1, oval2, oval3, invval, bool, global, local] in lst
:   py3 oname=vim.eval('oname')
:   py3 oval1=vim.bindeval('oval1')
:   py3 oval2=vim.bindeval('oval2')
:   py3 oval3=vim.bindeval('oval3')
:   if invval is 0 || invval is 1
:       py3 invval=bool(vim.bindeval('invval'))
:   else
:       py3 invval=vim.bindeval('invval')
:   endif
:   if bool
:       py3 oval1=bool(oval1)
:       py3 oval2=bool(oval2)
:       py3 oval3=bool(oval3)
:   endif
:   put ='>>> '.oname
:   $put ='  g/w/b:'.py3eval('oname in gset').'/'.py3eval('oname in wset').'/'.py3eval('oname in bset')
:   $put ='  g/w/b (in):'.py3eval('oname in gopts1').'/'.py3eval('oname in wopts1').'/'.py3eval('oname in bopts1')
:   for v in ['gopts1', 'wopts1', 'bopts1']
:       try
:           put ='  p/'.v.': '.Ev('repr('.v.'['''.oname.'''])')
:       catch
:           put ='  p/'.v.'! '.v:exception
:       endtry
:       let r=E(v.'['''.oname.''']=invval')
:       if r isnot 0
:           put ='  inv: '.string(invval).'! '.r
:       endif
:       for vv in (v is# 'gopts1' ? [v] : [v, v[:-2].'2', v[:-2].'3'])
:           let val=substitute(vv, '^.opts', 'oval', '')
:           let r=E(vv.'['''.oname.''']='.val)
:           if r isnot 0
:               put ='  '.vv.'! '.r
:           endif
:       endfor
:   endfor
:   call RecVars(oname)
:   for v in ['wopts3', 'bopts3']
:       let r=E('del '.v.'["'.oname.'"]')
:       if r isnot 0
:           put ='  del '.v.'! '.r
:       endif
:   endfor
:   call RecVars(oname)
:endfor
:delfunction RecVars
:delfunction E
:delfunction Ev
:py3 del ev
:py3 del e
:only
:for buf in g:bufs[1:]
:   execute 'bwipeout!' buf
:endfor
:py3 del gopts1
:py3 del wopts1
:py3 del wopts2
:py3 del wopts3
:py3 del bopts1
:py3 del bopts2
:py3 del bopts3
:py3 del oval1
:py3 del oval2
:py3 del oval3
:py3 del oname
:py3 del invval
:"
:" Test buffer object
:vnew
:put ='First line'
:put ='Second line'
:put ='Third line'
:1 delete _
:py3 b=vim.current.buffer
:wincmd w
:mark a
:augroup BUFS
:   autocmd BufFilePost * python3 cb.append(vim.eval('expand("<abuf>")') + ':BufFilePost:' + vim.eval('bufnr("%")'))
:   autocmd BufFilePre * python3 cb.append(vim.eval('expand("<abuf>")') + ':BufFilePre:' + vim.eval('bufnr("%")'))
:augroup END
py3 << EOF
# Tests BufferAppend and BufferItem
cb.append(b[0])
# Tests BufferSlice and BufferAssSlice
cb.append('abc5') # Will be overwritten
cb[-1:] = b[:-2]
# Test BufferLength and BufferAssSlice
cb.append('def') # Will not be overwritten
cb[len(cb):] = b[:]
# Test BufferAssItem and BufferMark
cb.append('ghi') # Will be overwritten
cb[-1] = repr((len(cb) - cb.mark('a')[0], cb.mark('a')[1]))
# Test BufferRepr
cb.append(repr(cb) + repr(b))
# Modify foreign buffer
b.append('foo')
b[0]='bar'
b[0:0]=['baz']
vim.command('call append("$", getbufline(%i, 1, "$"))' % b.number)
# Test assigning to name property
import os
old_name = cb.name
cb.name = 'foo'
cb.append(cb.name[-11:].replace(os.path.sep, '/'))
b.name = 'bar'
cb.append(b.name[-11:].replace(os.path.sep, '/'))
cb.name = old_name
cb.append(cb.name[-17:].replace(os.path.sep, '/'))
del old_name
# Test CheckBuffer
for _b in vim.buffers:
    if _b is not cb:
        vim.command('bwipeout! ' + str(_b.number))
del _b
cb.append('valid: b:%s, cb:%s' % (repr(b.valid), repr(cb.valid)))
for expr in ('b[1]','b[:] = ["A", "B"]','b[:]','b.append("abc6")'):
    try:
        exec(expr)
    except vim.error:
        pass
    else:
        # Usually a SEGV here
        # Should not happen in any case
        cb.append('No exception for ' + expr)
vim.command('cd .')
del b
EOF
:"
:" Test vim.buffers object
:set hidden
:edit a
:buffer #
:edit b
:buffer #
:edit c
:buffer #
py3 << EOF
# Check GCing iterator that was not fully exhausted
i = iter(vim.buffers)
cb.append('i:' + str(next(i)))
# and also check creating more than one iterator at a time
i2 = iter(vim.buffers)
cb.append('i2:' + str(next(i2)))
cb.append('i:' + str(next(i)))
# The following should trigger GC and not cause any problems
del i
del i2
i3 = iter(vim.buffers)
cb.append('i3:' + str(next(i3)))
del i3

prevnum = 0
for b in vim.buffers:
    # Check buffer order
    if prevnum >= b.number:
        cb.append('!!! Buffer numbers not in strictly ascending order')
    # Check indexing: vim.buffers[number].number == number
    cb.append(str(b.number) + ':' + repr(vim.buffers[b.number]) + '=' + repr(b))
    prevnum = b.number
del prevnum

cb.append(str(len(vim.buffers)))

bnums = list(map(lambda b: b.number, vim.buffers))[1:]

# Test wiping out buffer with existing iterator
i4 = iter(vim.buffers)
cb.append('i4:' + str(next(i4)))
vim.command('bwipeout! ' + str(bnums.pop(0)))
try:
    next(i4)
except vim.error:
    pass
else:
    cb.append('!!!! No vim.error')
i4 = iter(vim.buffers)
vim.command('bwipeout! ' + str(bnums.pop(-1)))
vim.command('bwipeout! ' + str(bnums.pop(-1)))
cb.append('i4:' + str(next(i4)))
try:
    next(i4)
except StopIteration:
    cb.append('StopIteration')
del i4
del bnums
EOF
:"
:" Test vim.{tabpage,window}list and vim.{tabpage,window} objects
:tabnew 0
:tabnew 1
:vnew a.1
:tabnew 2
:vnew a.2
:vnew b.2
:vnew c.2
py3 << EOF
cb.append('Number of tabs: ' + str(len(vim.tabpages)))
cb.append('Current tab pages:')

def W(w):
    if '(unknown)' in repr(w):
        return '<window object (unknown)>'
    else:
        return repr(w)

def Cursor(w, start=len(cb)):
    if w.buffer is cb:
        return repr((start - w.cursor[0], w.cursor[1]))
    else:
        return repr(w.cursor)

for t in vim.tabpages:
    cb.append('  ' + repr(t) + '(' + str(t.number) + ')' + ': ' + str(len(t.windows)) + ' windows, current is ' + W(t.window))
    cb.append('  Windows:')
    for w in t.windows:
        cb.append('    ' + W(w) + '(' + str(w.number) + ')' + ': displays buffer ' + repr(w.buffer) + '; cursor is at ' + Cursor(w))
        # Other values depend on the size of the terminal, so they are checked partly:
        for attr in ('height', 'row', 'width', 'col'):
            try:
                aval = getattr(w, attr)
                if type(aval) is not int:
                    raise TypeError
                if aval < 0:
                    raise ValueError
            except Exception as e:
                cb.append('!!!!!! Error while getting attribute ' + attr + ': ' + e.__class__.__name__)
        del aval
        del attr
        w.cursor = (len(w.buffer), 0)
del W
del Cursor
cb.append('Number of windows in current tab page: ' + str(len(vim.windows)))
if list(vim.windows) != list(vim.current.tabpage.windows):
    cb.append('!!!!!! Windows differ')
EOF
:"
:" Test vim.current
py3 << EOF
def H(o):
    return repr(o)
cb.append('Current tab page: ' + repr(vim.current.tabpage))
cb.append('Current window: ' + repr(vim.current.window) + ': ' + H(vim.current.window) + ' is ' + H(vim.current.tabpage.window))
cb.append('Current buffer: ' + repr(vim.current.buffer) + ': ' + H(vim.current.buffer) + ' is ' + H(vim.current.window.buffer)+ ' is ' + H(vim.current.tabpage.window.buffer))
del H
# Assigning: fails
try:
    vim.current.window = vim.tabpages[0].window
except ValueError:
    cb.append('ValueError at assigning foreign tab window')

for attr in ('window', 'tabpage', 'buffer'):
    try:
        setattr(vim.current, attr, None)
    except TypeError:
        cb.append('Type error at assigning None to vim.current.' + attr)
del attr

# Assigning: success
vim.current.tabpage = vim.tabpages[-2]
vim.current.buffer = cb
vim.current.window = vim.windows[0]
vim.current.window.cursor = (len(vim.current.buffer), 0)
cb.append('Current tab page: ' + repr(vim.current.tabpage))
cb.append('Current window: ' + repr(vim.current.window))
cb.append('Current buffer: ' + repr(vim.current.buffer))
cb.append('Current line: ' + repr(vim.current.line))
ws = list(vim.windows)
ts = list(vim.tabpages)
for b in vim.buffers:
    if b is not cb:
        vim.command('bwipeout! ' + str(b.number))
del b
cb.append('w.valid: ' + repr([w.valid for w in ws]))
cb.append('t.valid: ' + repr([t.valid for t in ts]))
del w
del t
del ts
del ws
EOF
:tabonly!
:only!
:"
:" Test types
py3 << EOF
for expr, attr in (
    ('vim.vars',                         'Dictionary'),
    ('vim.options',                      'Options'),
    ('vim.bindeval("{}")',               'Dictionary'),
    ('vim.bindeval("[]")',               'List'),
    ('vim.bindeval("function(\'tr\')")', 'Function'),
    ('vim.current.buffer',               'Buffer'),
    ('vim.current.range',                'Range'),
    ('vim.current.window',               'Window'),
    ('vim.current.tabpage',              'TabPage'),
):
    cb.append(expr + ':' + attr + ':' + repr(type(eval(expr)) is getattr(vim, attr)))
del expr
del attr
EOF
:"
:" Test __dir__() method
py3 << EOF
for name, o in (
        ('current',    vim.current),
        ('buffer',     vim.current.buffer),
        ('window',     vim.current.window),
        ('tabpage',    vim.current.tabpage),
        ('range',      vim.current.range),
        ('dictionary', vim.bindeval('{}')),
        ('list',       vim.bindeval('[]')),
        ('function',   vim.bindeval('function("tr")')),
        ('output',     sys.stdout),
    ):
    cb.append(name + ':' + ','.join(dir(o)))
del name
del o
EOF
:"
:" Test vim.*.__new__
:$put =string(py3eval('vim.Dictionary({})'))
:$put =string(py3eval('vim.Dictionary(a=1)'))
:$put =string(py3eval('vim.Dictionary(((''a'', 1),))'))
:$put =string(py3eval('vim.List()'))
:$put =string(py3eval('vim.List(iter(''abc7''))'))
:$put =string(py3eval('vim.Function(''tr'')'))
:$put =string(py3eval('vim.Function(''tr'', args=[123, 3, 4])'))
:$put =string(py3eval('vim.Function(''tr'', args=[])'))
:$put =string(py3eval('vim.Function(''tr'', self={})'))
:$put =string(py3eval('vim.Function(''tr'', args=[123, 3, 4], self={})'))
:$put ='auto_rebind'
:$put =string(py3eval('vim.Function(''tr'', auto_rebind=False)'))
:$put =string(py3eval('vim.Function(''tr'', args=[123, 3, 4], auto_rebind=False)'))
:$put =string(py3eval('vim.Function(''tr'', args=[], auto_rebind=False)'))
:$put =string(py3eval('vim.Function(''tr'', self={}, auto_rebind=False)'))
:$put =string(py3eval('vim.Function(''tr'', args=[123, 3, 4], self={}, auto_rebind=False)'))
:"
:" Test vim.Function
:function Args(...)
:   return a:000
:endfunction
:function SelfArgs(...) dict
:   return [a:000, self]
:endfunction
:" The following four lines should not crash
:let Pt = function('tr', [[]], {'l': []})
:py3 Pt = vim.bindeval('Pt')
:unlet Pt
:py3 del Pt
py3 << EOF
def ecall(out_prefix, func, *args, **kwargs):
    line = out_prefix + ': '
    try:
        ret = func(*args, **kwargs)
    except Exception:
        line += '!exception: ' + emsg(sys.exc_info())
    else:
        line += '!result: ' + str(vim.Function('string')(ret), 'utf-8')
    cb.append(line)
a = vim.Function('Args')
pa1 = vim.Function('Args', args=['abcArgsPA1'])
pa2 = vim.Function('Args', args=[])
pa3 = vim.Function('Args', args=['abcArgsPA3'], self={'abcSelfPA3': 'abcSelfPA3Val'})
pa4 = vim.Function('Args', self={'abcSelfPA4': 'abcSelfPA4Val'})
cb.append('a: ' + repr(a))
cb.append('pa1: ' + repr(pa1))
cb.append('pa2: ' + repr(pa2))
cb.append('pa3: ' + repr(pa3))
cb.append('pa4: ' + repr(pa4))
sa = vim.Function('SelfArgs')
psa1 = vim.Function('SelfArgs', args=['abcArgsPSA1'])
psa2 = vim.Function('SelfArgs', args=[])
psa3 = vim.Function('SelfArgs', args=['abcArgsPSA3'], self={'abcSelfPSA3': 'abcSelfPSA3Val'})
psa4 = vim.Function('SelfArgs', self={'abcSelfPSA4': 'abcSelfPSA4Val'})
psa5 = vim.Function('SelfArgs', self={'abcSelfPSA5': 'abcSelfPSA5Val'}, auto_rebind=0)
psa6 = vim.Function('SelfArgs', args=['abcArgsPSA6'], self={'abcSelfPSA6': 'abcSelfPSA6Val'}, auto_rebind=())
psa7 = vim.Function('SelfArgs', args=['abcArgsPSA7'], auto_rebind=[])
psa8 = vim.Function('SelfArgs', auto_rebind=False)
psa9 = vim.Function('SelfArgs', self={'abcSelfPSA9': 'abcSelfPSA9Val'}, auto_rebind=True)
psaA = vim.Function('SelfArgs', args=['abcArgsPSAA'], self={'abcSelfPSAA': 'abcSelfPSAAVal'}, auto_rebind=1)
psaB = vim.Function('SelfArgs', args=['abcArgsPSAB'], auto_rebind={'abcARPSAB': 'abcARPSABVal'})
psaC = vim.Function('SelfArgs', auto_rebind=['abcARPSAC'])
cb.append('sa: ' + repr(sa))
cb.append('psa1: ' + repr(psa1))
cb.append('psa2: ' + repr(psa2))
cb.append('psa3: ' + repr(psa3))
cb.append('psa4: ' + repr(psa4))
cb.append('psa5: ' + repr(psa5))
cb.append('psa6: ' + repr(psa6))
cb.append('psa7: ' + repr(psa7))
cb.append('psa8: ' + repr(psa8))
cb.append('psa9: ' + repr(psa9))
cb.append('psaA: ' + repr(psaA))
cb.append('psaB: ' + repr(psaB))
cb.append('psaC: ' + repr(psaC))

psar = vim.Function('SelfArgs', args=[{'abcArgsPSAr': 'abcArgsPSArVal'}], self={'abcSelfPSAr': 'abcSelfPSArVal'})
psar.args[0]['abcArgsPSAr2'] = [psar.self, psar.args[0]]
psar.self['rec'] = psar
psar.self['self'] = psar.self
psar.self['args'] = psar.args

try:
    cb.append('psar: ' + repr(psar))
except Exception:
    cb.append('!!!!!!!! Caught exception: ' + emsg(sys.exc_info()))
EOF
:$put ='s(a): '.string(py3eval('a'))
:$put ='s(pa1): '.string(py3eval('pa1'))
:$put ='s(pa2): '.string(py3eval('pa2'))
:$put ='s(pa3): '.string(py3eval('pa3'))
:$put ='s(pa4): '.string(py3eval('pa4'))
:$put ='s(sa): '.string(py3eval('sa'))
:$put ='s(psa1): '.string(py3eval('psa1'))
:$put ='s(psa2): '.string(py3eval('psa2'))
:$put ='s(psa3): '.string(py3eval('psa3'))
:$put ='s(psa4): '.string(py3eval('psa4'))
:$put ='s(psa5): '.string(py3eval('psa5'))
:$put ='s(psa6): '.string(py3eval('psa6'))
:$put ='s(psa7): '.string(py3eval('psa7'))
:$put ='s(psa8): '.string(py3eval('psa8'))
:$put ='s(psa9): '.string(py3eval('psa9'))
:$put ='s(psaA): '.string(py3eval('psaA'))
:$put ='s(psaB): '.string(py3eval('psaB'))
:$put ='s(psaC): '.string(py3eval('psaC'))
:
:for v in ['sa', 'psa1', 'psa2', 'psa3', 'psa4', 'psa5', 'psa6', 'psa7', 'psa8', 'psa9', 'psaA', 'psaB', 'psaC']
:   let d = {'f': py3eval(v)}
:   $put ='d.'.v.'(): '.string(d.f())
:endfor
:
:py3 ecall('a()', a, )
:py3 ecall('pa1()', pa1, )
:py3 ecall('pa2()', pa2, )
:py3 ecall('pa3()', pa3, )
:py3 ecall('pa4()', pa4, )
:py3 ecall('sa()', sa, )
:py3 ecall('psa1()', psa1, )
:py3 ecall('psa2()', psa2, )
:py3 ecall('psa3()', psa3, )
:py3 ecall('psa4()', psa4, )
:
:py3 ecall('a(42, 43)', a, 42, 43)
:py3 ecall('pa1(42, 43)', pa1, 42, 43)
:py3 ecall('pa2(42, 43)', pa2, 42, 43)
:py3 ecall('pa3(42, 43)', pa3, 42, 43)
:py3 ecall('pa4(42, 43)', pa4, 42, 43)
:py3 ecall('sa(42, 43)', sa, 42, 43)
:py3 ecall('psa1(42, 43)', psa1, 42, 43)
:py3 ecall('psa2(42, 43)', psa2, 42, 43)
:py3 ecall('psa3(42, 43)', psa3, 42, 43)
:py3 ecall('psa4(42, 43)', psa4, 42, 43)
:
:py3 ecall('a(42, self={"20": 1})', a, 42, self={'20': 1})
:py3 ecall('pa1(42, self={"20": 1})', pa1, 42, self={'20': 1})
:py3 ecall('pa2(42, self={"20": 1})', pa2, 42, self={'20': 1})
:py3 ecall('pa3(42, self={"20": 1})', pa3, 42, self={'20': 1})
:py3 ecall('pa4(42, self={"20": 1})', pa4, 42, self={'20': 1})
:py3 ecall('sa(42, self={"20": 1})', sa, 42, self={'20': 1})
:py3 ecall('psa1(42, self={"20": 1})', psa1, 42, self={'20': 1})
:py3 ecall('psa2(42, self={"20": 1})', psa2, 42, self={'20': 1})
:py3 ecall('psa3(42, self={"20": 1})', psa3, 42, self={'20': 1})
:py3 ecall('psa4(42, self={"20": 1})', psa4, 42, self={'20': 1})
:
:py3 ecall('a(self={"20": 1})', a, self={'20': 1})
:py3 ecall('pa1(self={"20": 1})', pa1, self={'20': 1})
:py3 ecall('pa2(self={"20": 1})', pa2, self={'20': 1})
:py3 ecall('pa3(self={"20": 1})', pa3, self={'20': 1})
:py3 ecall('pa4(self={"20": 1})', pa4, self={'20': 1})
:py3 ecall('sa(self={"20": 1})', sa, self={'20': 1})
:py3 ecall('psa1(self={"20": 1})', psa1, self={'20': 1})
:py3 ecall('psa2(self={"20": 1})', psa2, self={'20': 1})
:py3 ecall('psa3(self={"20": 1})', psa3, self={'20': 1})
:py3 ecall('psa4(self={"20": 1})', psa4, self={'20': 1})
py3 << EOF
def s(v):
    if v is None:
        return repr(v)
    else:
        return str(vim.Function('string')(v), 'utf-8')

cb.append('a.args: ' + s(a.args))
cb.append('pa1.args: ' + s(pa1.args))
cb.append('pa2.args: ' + s(pa2.args))
cb.append('pa3.args: ' + s(pa3.args))
cb.append('pa4.args: ' + s(pa4.args))
cb.append('sa.args: ' + s(sa.args))
cb.append('psa1.args: ' + s(psa1.args))
cb.append('psa2.args: ' + s(psa2.args))
cb.append('psa3.args: ' + s(psa3.args))
cb.append('psa4.args: ' + s(psa4.args))

cb.append('a.self: ' + s(a.self))
cb.append('pa1.self: ' + s(pa1.self))
cb.append('pa2.self: ' + s(pa2.self))
cb.append('pa3.self: ' + s(pa3.self))
cb.append('pa4.self: ' + s(pa4.self))
cb.append('sa.self: ' + s(sa.self))
cb.append('psa1.self: ' + s(psa1.self))
cb.append('psa2.self: ' + s(psa2.self))
cb.append('psa3.self: ' + s(psa3.self))
cb.append('psa4.self: ' + s(psa4.self))

cb.append('a.name: ' + s(a.name))
cb.append('pa1.name: ' + s(pa1.name))
cb.append('pa2.name: ' + s(pa2.name))
cb.append('pa3.name: ' + s(pa3.name))
cb.append('pa4.name: ' + s(pa4.name))
cb.append('sa.name: ' + s(sa.name))
cb.append('psa1.name: ' + s(psa1.name))
cb.append('psa2.name: ' + s(psa2.name))
cb.append('psa3.name: ' + s(psa3.name))
cb.append('psa4.name: ' + s(psa4.name))

cb.append('a.auto_rebind: ' + s(a.auto_rebind))
cb.append('pa1.auto_rebind: ' + s(pa1.auto_rebind))
cb.append('pa2.auto_rebind: ' + s(pa2.auto_rebind))
cb.append('pa3.auto_rebind: ' + s(pa3.auto_rebind))
cb.append('pa4.auto_rebind: ' + s(pa4.auto_rebind))
cb.append('sa.auto_rebind: ' + s(sa.auto_rebind))
cb.append('psa1.auto_rebind: ' + s(psa1.auto_rebind))
cb.append('psa2.auto_rebind: ' + s(psa2.auto_rebind))
cb.append('psa3.auto_rebind: ' + s(psa3.auto_rebind))
cb.append('psa4.auto_rebind: ' + s(psa4.auto_rebind))
cb.append('psa5.auto_rebind: ' + s(psa5.auto_rebind))
cb.append('psa6.auto_rebind: ' + s(psa6.auto_rebind))
cb.append('psa7.auto_rebind: ' + s(psa7.auto_rebind))
cb.append('psa8.auto_rebind: ' + s(psa8.auto_rebind))
cb.append('psa9.auto_rebind: ' + s(psa9.auto_rebind))
cb.append('psaA.auto_rebind: ' + s(psaA.auto_rebind))
cb.append('psaB.auto_rebind: ' + s(psaB.auto_rebind))
cb.append('psaC.auto_rebind: ' + s(psaC.auto_rebind))

del s

del a
del pa1
del pa2
del pa3
del pa4
del sa
del psa1
del psa2
del psa3
del psa4
del psa5
del psa6
del psa7
del psa8
del psa9
del psaA
del psaB
del psaC
del psar

del ecall
EOF
:"
:" Test stdout/stderr
:redir => messages
:py3 sys.stdout.write('abc8') ; sys.stdout.write('def')
:py3 sys.stderr.write('abc9') ; sys.stderr.write('def')
:py3 sys.stdout.writelines(iter('abcA'))
:py3 sys.stderr.writelines(iter('abcB'))
:redir END
:$put =string(substitute(messages, '\d\+', '', 'g'))
:" Test subclassing
:fun Put(...)
:   $put =string(a:000)
:   return a:000
:endfun
py3 << EOF
class DupDict(vim.Dictionary):
    def __setitem__(self, key, value):
        super(DupDict, self).__setitem__(key, value)
        super(DupDict, self).__setitem__('dup_' + key, value)
dd = DupDict()
dd['a'] = 'b'

class DupList(vim.List):
    def __getitem__(self, idx):
        return [super(DupList, self).__getitem__(idx)] * 2

dl = DupList()
dl2 = DupList(iter('abcC'))
dl.extend(dl2[0])

class DupFun(vim.Function):
    def __call__(self, arg):
        return super(DupFun, self).__call__(arg, arg)

df = DupFun('Put')
EOF
:$put =string(sort(keys(py3eval('dd'))))
:$put =string(py3eval('dl'))
:$put =string(py3eval('dl2'))
:$put =string(py3eval('df(2)'))
:$put =string(py3eval('dl') is# py3eval('dl'))
:$put =string(py3eval('dd') is# py3eval('dd'))
:$put =string(py3eval('df'))
:delfunction Put
py3 << EOF
del DupDict
del DupList
del DupFun
del dd
del dl
del dl2
del df
EOF
:"
:" Test chdir
py3 << EOF
import os
fnamemodify = vim.Function('fnamemodify')
cb.append(str(fnamemodify('.', ':p:h:t')))
cb.append(vim.eval('@%'))
os.chdir('..')
path = fnamemodify('.', ':p:h:t')
if path != b'src':
  # Running tests from a shadow directory, so move up another level
  # This will result in @% looking like shadow/testdir/test87.in, hence the
  # slicing to remove the leading path and path separator
  os.chdir('..')
  cb.append(str(fnamemodify('.', ':p:h:t')))
  cb.append(vim.eval('@%')[len(path)+1:].replace(os.path.sep, '/'))
  os.chdir(path)
else:
  cb.append(str(fnamemodify('.', ':p:h:t')))
  cb.append(vim.eval('@%').replace(os.path.sep, '/'))
del path
os.chdir('testdir')
cb.append(str(fnamemodify('.', ':p:h:t')))
cb.append(vim.eval('@%'))
del fnamemodify
EOF
:"
:" Test errors
:fun F() dict
:endfun
:fun D()
:endfun
py3 << EOF
d = vim.Dictionary()
ned = vim.Dictionary(foo='bar', baz='abcD')
dl = vim.Dictionary(a=1)
dl.locked = True
l = vim.List()
ll = vim.List('abcE')
ll.locked = True
nel = vim.List('abcO')
f = vim.Function('string')
fd = vim.Function('F')
fdel = vim.Function('D')
vim.command('delfunction D')

def subexpr_test(expr, name, subexprs):
    cb.append('>>> Testing %s using %s' % (name, expr))
    for subexpr in subexprs:
        ee(expr % subexpr)
    cb.append('<<< Finished')

def stringtochars_test(expr):
    return subexpr_test(expr, 'StringToChars', (
        '1',       # Fail type checks
        'b"\\0"',  # Fail PyString_AsStringAndSize(object, , NULL) check
        '"\\0"',   # Fail PyString_AsStringAndSize(bytes, , NULL) check
    ))

class Mapping(object):
    def __init__(self, d):
        self.d = d

    def __getitem__(self, key):
        return self.d[key]

    def keys(self):
        return self.d.keys()

    def items(self):
        return self.d.items()

def convertfrompyobject_test(expr, recurse=True):
    # pydict_to_tv
    stringtochars_test(expr % '{%s : 1}')
    if recurse:
        convertfrompyobject_test(expr % '{"abcF" : %s}', False)
    # pymap_to_tv
    stringtochars_test(expr % 'Mapping({%s : 1})')
    if recurse:
        convertfrompyobject_test(expr % 'Mapping({"abcG" : %s})', False)
    # pyseq_to_tv
    iter_test(expr)
    return subexpr_test(expr, 'ConvertFromPyObject', (
        'None',                 # Not conversible
        '{b"": 1}',             # Empty key not allowed
        '{"": 1}',              # Same, but with unicode object
        'FailingMapping()',     #
        'FailingMappingKey()',  #
        'FailingNumber()',      #
    ))

def convertfrompymapping_test(expr):
    convertfrompyobject_test(expr)
    return subexpr_test(expr, 'ConvertFromPyMapping', (
        '[]',
    ))

def iter_test(expr):
    return subexpr_test(expr, '*Iter*', (
        'FailingIter()',
        'FailingIterNext()',
    ))

def number_test(expr, natural=False, unsigned=False):
    if natural:
        unsigned = True
    return subexpr_test(expr, 'NumberToLong', (
        '[]',
        'None',
    ) + (('-1',) if unsigned else ())
    + (('0',) if natural else ()))

class FailingTrue(object):
    def __bool__(self):
        raise NotImplementedError('bool')

class FailingIter(object):
    def __iter__(self):
        raise NotImplementedError('iter')

class FailingIterNext(object):
    def __iter__(self):
        return self

    def __next__(self):
        raise NotImplementedError('next')

class FailingIterNextN(object):
    def __init__(self, n):
        self.n = n

    def __iter__(self):
        return self

    def __next__(self):
        if self.n:
            self.n -= 1
            return 1
        else:
            raise NotImplementedError('next N')

class FailingMappingKey(object):
    def __getitem__(self, item):
        raise NotImplementedError('getitem:mappingkey')

    def keys(self):
        return list("abcH")

class FailingMapping(object):
    def __getitem__(self):
        raise NotImplementedError('getitem:mapping')

    def keys(self):
        raise NotImplementedError('keys')

class FailingList(list):
    def __getitem__(self, idx):
        if i == 2:
            raise NotImplementedError('getitem:list')
        else:
            return super(FailingList, self).__getitem__(idx)

class NoArgsCall(object):
    def __call__(self):
        pass

class FailingCall(object):
    def __call__(self, path):
        raise NotImplementedError('call')

class FailingNumber(object):
    def __int__(self):
        raise NotImplementedError('int')

cb.append("> Output")
cb.append(">> OutputSetattr")
ee('del sys.stdout.softspace')
number_test('sys.stdout.softspace = %s', unsigned=True)
number_test('sys.stderr.softspace = %s', unsigned=True)
ee('assert sys.stdout.isatty()==False')
ee('assert sys.stdout.seekable()==False')
ee('sys.stdout.close()')
ee('sys.stdout.flush()')
ee('assert sys.stderr.isatty()==False')
ee('assert sys.stderr.seekable()==False')
ee('sys.stderr.close()')
ee('sys.stderr.flush()')
ee('sys.stdout.attr = None')
cb.append(">> OutputWrite")
ee('assert sys.stdout.writable()==True')
ee('assert sys.stdout.readable()==False')
ee('assert sys.stderr.writable()==True')
ee('assert sys.stderr.readable()==False')
ee('assert sys.stdout.closed()==False')
ee('assert sys.stderr.closed()==False')
ee('assert sys.stdout.errors=="strict"')
ee('assert sys.stderr.errors=="strict"')
ee('assert sys.stdout.encoding==sys.stderr.encoding')
ee('sys.stdout.write(None)')
cb.append(">> OutputWriteLines")
ee('sys.stdout.writelines(None)')
ee('sys.stdout.writelines([1])')
iter_test('sys.stdout.writelines(%s)')
cb.append("> VimCommand")
stringtochars_test('vim.command(%s)')
ee('vim.command("", 2)')
#! Not checked: vim->python exceptions translating: checked later
cb.append("> VimToPython")
#! Not checked: everything: needs errors in internal python functions
cb.append("> VimEval")
stringtochars_test('vim.eval(%s)')
ee('vim.eval("", FailingTrue())')
#! Not checked: everything: needs errors in internal python functions
cb.append("> VimEvalPy")
stringtochars_test('vim.bindeval(%s)')
ee('vim.eval("", 2)')
#! Not checked: vim->python exceptions translating: checked later
cb.append("> VimStrwidth")
stringtochars_test('vim.strwidth(%s)')
cb.append("> VimForeachRTP")
ee('vim.foreach_rtp(None)')
ee('vim.foreach_rtp(NoArgsCall())')
ee('vim.foreach_rtp(FailingCall())')
ee('vim.foreach_rtp(int, 2)')
cb.append('> import')
old_rtp = vim.options['rtp']
vim.options['rtp'] = os.getcwd().replace('\\', '\\\\').replace(',', '\\,')
ee('import xxx_no_such_module_xxx')
ee('import failing_import')
ee('import failing')
vim.options['rtp'] = old_rtp
del old_rtp
cb.append("> Options")
cb.append(">> OptionsItem")
ee('vim.options["abcQ"]')
ee('vim.options[""]')
stringtochars_test('vim.options[%s]')
cb.append(">> OptionsContains")
stringtochars_test('%s in vim.options')
cb.append("> Dictionary")
cb.append(">> DictionaryConstructor")
ee('vim.Dictionary("abcI")')
##! Not checked: py_dict_alloc failure
cb.append(">> DictionarySetattr")
ee('del d.locked')
ee('d.locked = FailingTrue()')
ee('vim.vvars.locked = False')
ee('d.scope = True')
ee('d.xxx = True')
cb.append(">> _DictionaryItem")
ee('d.get("a", 2, 3)')
stringtochars_test('d.get(%s)')
ee('d.pop("a")')
ee('dl.pop("a")')
cb.append(">> DictionaryContains")
ee('"" in d')
ee('0 in d')
cb.append(">> DictionaryIterNext")
ee('for i in ned: ned["a"] = 1')
del i
cb.append(">> DictionaryAssItem")
ee('dl["b"] = 1')
stringtochars_test('d[%s] = 1')
convertfrompyobject_test('d["a"] = %s')
cb.append(">> DictionaryUpdate")
cb.append(">>> kwargs")
cb.append(">>> iter")
ee('d.update(FailingMapping())')
ee('d.update([FailingIterNext()])')
ee('d.update([FailingIterNextN(1)])')
iter_test('d.update(%s)')
convertfrompyobject_test('d.update(%s)')
stringtochars_test('d.update(((%s, 0),))')
convertfrompyobject_test('d.update((("a", %s),))')
cb.append(">> DictionaryPopItem")
ee('d.popitem(1, 2)')
cb.append(">> DictionaryHasKey")
ee('d.has_key()')
cb.append("> List")
cb.append(">> ListConstructor")
ee('vim.List(1, 2)')
ee('vim.List(a=1)')
iter_test('vim.List(%s)')
convertfrompyobject_test('vim.List([%s])')
cb.append(">> ListItem")
ee('l[1000]')
cb.append(">> ListAssItem")
ee('ll[1] = 2')
ee('l[1000] = 3')
cb.append(">> ListAssSlice")
ee('ll[1:100] = "abcJ"')
iter_test('l[:] = %s')
ee('nel[1:10:2]  = "abcK"')
cb.append(repr(tuple(nel)))
ee('nel[1:10:2]  = "a"')
cb.append(repr(tuple(nel)))
ee('nel[1:1:-1]  = "a"')
cb.append(repr(tuple(nel)))
ee('nel[:] = FailingIterNextN(2)')
cb.append(repr(tuple(nel)))
convertfrompyobject_test('l[:] = [%s]')
cb.append(">> ListConcatInPlace")
iter_test('l.extend(%s)')
convertfrompyobject_test('l.extend([%s])')
cb.append(">> ListSetattr")
ee('del l.locked')
ee('l.locked = FailingTrue()')
ee('l.xxx = True')
cb.append("> Function")
cb.append(">> FunctionConstructor")
cb.append(">>> FunctionConstructor")
ee('vim.Function("123")')
ee('vim.Function("xxx_non_existent_function_xxx")')
ee('vim.Function("xxx#non#existent#function#xxx")')
ee('vim.Function("xxx_non_existent_function_xxx2", args=[])')
ee('vim.Function("xxx_non_existent_function_xxx3", self={})')
ee('vim.Function("xxx_non_existent_function_xxx4", args=[], self={})')
cb.append(">>> FunctionNew")
ee('vim.Function("tr", self="abcFuncSelf")')
ee('vim.Function("tr", args=427423)')
ee('vim.Function("tr", self="abcFuncSelf2", args="abcFuncArgs2")')
ee('vim.Function(self="abcFuncSelf2", args="abcFuncArgs2")')
ee('vim.Function("tr", "", self="abcFuncSelf2", args="abcFuncArgs2")')
ee('vim.Function("tr", "")')
cb.append(">> FunctionCall")
convertfrompyobject_test('f(%s)')
convertfrompymapping_test('fd(self=%s)')
cb.append("> TabPage")
cb.append(">> TabPageAttr")
ee('vim.current.tabpage.xxx')
cb.append("> TabList")
cb.append(">> TabListItem")
ee('vim.tabpages[1000]')
cb.append("> Window")
cb.append(">> WindowAttr")
ee('vim.current.window.xxx')
cb.append(">> WindowSetattr")
ee('vim.current.window.buffer = 0')
ee('vim.current.window.cursor = (100000000, 100000000)')
ee('vim.current.window.cursor = True')
number_test('vim.current.window.height = %s', unsigned=True)
number_test('vim.current.window.width = %s', unsigned=True)
ee('vim.current.window.xxxxxx = True')
cb.append("> WinList")
cb.append(">> WinListItem")
ee('vim.windows[1000]')
cb.append("> Buffer")
cb.append(">> StringToLine (indirect)")
ee('vim.current.buffer[0] = "\\na"')
ee('vim.current.buffer[0] = b"\\na"')
cb.append(">> SetBufferLine (indirect)")
ee('vim.current.buffer[0] = True')
cb.append(">> SetBufferLineList (indirect)")
ee('vim.current.buffer[:] = True')
ee('vim.current.buffer[:] = ["\\na", "bc"]')
cb.append(">> InsertBufferLines (indirect)")
ee('vim.current.buffer.append(None)')
ee('vim.current.buffer.append(["\\na", "bc"])')
ee('vim.current.buffer.append("\\nbc")')
cb.append(">> RBItem")
ee('vim.current.buffer[100000000]')
cb.append(">> RBAsItem")
ee('vim.current.buffer[100000000] = ""')
cb.append(">> BufferAttr")
ee('vim.current.buffer.xxx')
cb.append(">> BufferSetattr")
ee('vim.current.buffer.name = True')
ee('vim.current.buffer.xxx = True')
cb.append(">> BufferMark")
ee('vim.current.buffer.mark(0)')
ee('vim.current.buffer.mark("abcM")')
ee('vim.current.buffer.mark("!")')
cb.append(">> BufferRange")
ee('vim.current.buffer.range(1, 2, 3)')
cb.append("> BufMap")
cb.append(">> BufMapItem")
ee('vim.buffers[100000000]')
number_test('vim.buffers[%s]', natural=True)
cb.append("> Current")
cb.append(">> CurrentGetattr")
ee('vim.current.xxx')
cb.append(">> CurrentSetattr")
ee('vim.current.line = True')
ee('vim.current.buffer = True')
ee('vim.current.window = True')
ee('vim.current.tabpage = True')
ee('vim.current.xxx = True')
del d
del ned
del dl
del l
del ll
del nel
del f
del fd
del fdel
del subexpr_test
del stringtochars_test
del Mapping
del convertfrompyobject_test
del convertfrompymapping_test
del iter_test
del number_test
del FailingTrue
del FailingIter
del FailingIterNext
del FailingIterNextN
del FailingMapping
del FailingMappingKey
del FailingList
del NoArgsCall
del FailingCall
del FailingNumber
EOF
:delfunction F
:"
:" Test import
py3 << EOF
sys.path.insert(0, os.path.join(os.getcwd(), 'python_before'))
sys.path.append(os.path.join(os.getcwd(), 'python_after'))
vim.options['rtp'] = os.getcwd().replace(',', '\\,').replace('\\', '\\\\')
l = []
def callback(path):
    l.append(os.path.relpath(path))
vim.foreach_rtp(callback)
cb.append(repr(l))
del l
def callback(path):
    return os.path.relpath(path)
cb.append(repr(vim.foreach_rtp(callback)))
del callback
from module import dir as d
from modulex import ddir
cb.append(d + ',' + ddir)
import before
cb.append(before.dir)
import after
cb.append(after.dir)
import topmodule as tm
import topmodule.submodule as tms
import topmodule.submodule.subsubmodule.subsubsubmodule as tmsss
cb.append(tm.__file__.replace(os.path.sep, '/')[-len('modulex/topmodule/__init__.py'):])
cb.append(tms.__file__.replace(os.path.sep, '/')[-len('modulex/topmodule/submodule/__init__.py'):])
cb.append(tmsss.__file__.replace(os.path.sep, '/')[-len('modulex/topmodule/submodule/subsubmodule/subsubsubmodule.py'):])
del before
del after
del d
del ddir
del tm
del tms
del tmsss
EOF
:"
:" Test exceptions
:fun Exe(e)
:   execute a:e
:endfun
py3 << EOF
Exe = vim.bindeval('function("Exe")')
ee('vim.command("throw \'abcN\'")')
ee('Exe("throw \'def\'")')
ee('vim.eval("Exe(\'throw \'\'ghi\'\'\')")')
ee('vim.eval("Exe(\'echoerr \'\'jkl\'\'\')")')
ee('vim.eval("Exe(\'xxx_non_existent_command_xxx\')")')
ee('vim.eval("xxx_unknown_function_xxx()")')
ee('vim.bindeval("Exe(\'xxx_non_existent_command_xxx\')")')
del Exe
EOF
:delfunction Exe
:"
:" Regression: interrupting vim.command propagates to next vim.command
py3 << EOF
def test_keyboard_interrupt():
    try:
        vim.command('while 1 | endwhile')
    except KeyboardInterrupt:
        cb.append('Caught KeyboardInterrupt')
    except Exception:
        cb.append('!!!!!!!! Caught exception: ' + emsg(sys.exc_info()))
    else:
        cb.append('!!!!!!!! No exception')
    try:
        vim.command('$ put =\'Running :put\'')
    except KeyboardInterrupt:
        cb.append('!!!!!!!! Caught KeyboardInterrupt')
    except Exception:
        cb.append('!!!!!!!! Caught exception: ' + emsg(sys.exc_info()))
    else:
        cb.append('No exception')
EOF
:debuggreedy
:call inputsave()
:call feedkeys("s\ns\ns\ns\nq\n")
:redir => output
:debug silent! py3 test_keyboard_interrupt()
:redir END
:0 debuggreedy
:call inputrestore()
:silent $put =output
:unlet output
:py3 del test_keyboard_interrupt
:"
:" Cleanup
py3 << EOF
del cb
del ee
del emsg
del sys
del os
del vim
EOF
:endfun
:"
:fun RunTest()
:let checkrefs = !empty($PYTHONDUMPREFS)
:let start = getline(1, '$')
:for i in range(checkrefs ? 10 : 1)
:   if i != 0
:       %d _
:       call setline(1, start)
:   endif
:   call Test()
:   if i == 0
:       let result = getline(1, '$')
:   endif
:endfor
:if checkrefs
:   %d _
:   call setline(1, result)
:endif
:endfun
:"
:call RunTest()
:delfunction RunTest
:delfunction Test
:call garbagecollect(1)
:"
:/^start:/,$wq! test.out
:/^start:/,$w! test.out
:" vim: et ts=4 isk-=\:
:while getchar(0) isnot 0|endwhile
ENDTEST

start:
